package br.com.gaspar.utils;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/** @@
 * <p>Title: Arquivo</p>
 * <p>Description: Classe de manipulacao de arquivos</p>
 * <p>Copyright: Copyright (c) 2005</p>
 * <p>Company: Sefaz - GO/ Politec Gyn</p>
 * @author Josue Ferreira
 * @version 1.0
 */
public class ArquivoUtil {

    private static final Logger log = LoggerFactory.getLogger(ArquivoUtil.class);
    private static final int TAMANHO_BUFFER = 2048; // 2kb

    /**
     * M�todo obrigat�rio por causa da interface(FilenameFilter) implementada.
     * Tem como fun��o setar o filtro para pesquisa de arquivos.
     * @param dir diret�rio em forma de objeto File.
     * @param name nome do arquivo.
     * @return true se n�o ocorreu erro.
     * @return false se ocorreu algum erro.
     */
//    @Override
//    public boolean accept(File dir, String name) {
//        return (name.startsWith(nomeFiltro));
//    }
    /**
     * Move o arquivo de um diret�rio para outro
     * @param origem String
     * @param destino String
     * @throws Exception
     */
    public static boolean mover(File file, File filedest) {
        if (file == null || filedest == null) {
            return false;
        }

        FileOutputStream os = null;
        FileInputStream input = null;
        try {
            input = new FileInputStream(file);
            os = new FileOutputStream(filedest);
            garantirExistencia(filedest.getParentFile(), true);

            if (!file.renameTo(filedest) && file.length() > 0) {
                byte[] arquivoTemp = new byte[(int) file.length()];

                if (input.read(arquivoTemp) != arquivoTemp.length) {
                    log.warn("erro");
                    return false;
                }

                os.write(arquivoTemp);

                if (!file.delete()) {
                    log.warn("erro");
                    return false;
                }
            }
        } catch (FileNotFoundException ex) {
            log.error("FileNotFoundException", ex);
            return false;
        } catch (IOException ex) {
            log.error("IOException", ex);
            return false;
        } finally {

            try {

                if (input != null) {
                    input.close();
                }
                if (os != null) {
                    os.close();
                }

            } catch (IOException ex) {
                log.error("IOException", ex);
            }

        }
        return true;

    }

    public static boolean mover(String origem, String destino)
            throws IOException {
        if (origem == null || origem.isEmpty()
                || destino == null || destino.isEmpty()) {
            return false;
        }
        File file = new File(origem);
        File filedest = new File(destino);
        return mover(file, filedest);
    }

    /**
     * Salva um arquivo no diret�rio
     * @param filename String
     * @param str o conteudo a ser gravado.
     * @throws IOException
     */
    public static boolean salvar(String filename, String str)
            throws IOException {

        if (filename == null || str == null) {
            return false;
        }

        try {
            if (!garantirExistencia(new File(filename))) {
                return false;
            }
            Writer write = new OutputStreamWriter(new FileOutputStream(filename), "UTF-8");
            write.flush();
            write.write(str);
            write.close();
            return true;
        } catch (IOException ex) {
            log.error("IOException", ex);
            return false;
        }
    }

    public static boolean salvar(File arquivo, String content,
            String charset) throws IOException {
        if (content == null
                || charset == null || charset.isEmpty()) {
            return false;
        }
        return salvar(arquivo, content.getBytes(charset));
    }

    public static boolean salvar(File arquivo, String content)
            throws IOException {
        if (content == null) {
            return false;
        }
        return salvar(arquivo, content.getBytes("UTF-8"));
    }

    public static boolean salvar(File arquivo, byte[] content)
            throws IOException {

        if (arquivo == null || content == null || content.length == 0) {
            return false;
        }
        FileOutputStream fos = null;
        try {
            if (!garantirExistencia(arquivo)) {
                return false;
            }
            fos = new FileOutputStream(arquivo);
            fos.write(content);
            fos.close();
            return true;

        } catch (IOException ex) {
            log.error("IOException", ex);
            return false;
        } finally {
            try {
                fos.close();
            } catch (IOException ex) {
                log.error("IOException", ex);
            }

        }
    }

    /**
     * Metodo que lista todas os arquivos do diretorio.
     * @param caminho String
     * @throws Exception
     * @return ArrayList
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public static ArrayList listarDiretorio(String caminho)
            throws Exception {
        if (caminho == null) {
            return null;
        }
        File arquivo = new File(caminho);
        File[] lista = arquivo.listFiles();
        if (lista == null) {
            log.error("O diretorio informado nao existe.");
            return null;
        }
        if (lista.length == 0) {
            log.warn("Nao foi encontrado nenhum arquivo.");
            return null;
        }
        ArrayList listaArquivos = new ArrayList();
        for (int i = 0; i < lista.length; i++) {
            //Separa somente as fotos, deixando as pastas fora do array.
            if (!lista[i].isDirectory()) {
                listaArquivos.add(lista[i].getName());
            }
        }

        return listaArquivos;
    }

    static final void copiar(InputStream in, OutputStream out)
            throws IOException {
        byte[] buffer = new byte[1024];
        int len;
        while ((len = in.read(buffer)) >= 0) {
            out.write(buffer, 0, len);
        }
        in.close();
        out.close();
    }

    public static boolean copiar(File origem, File destino, String charset) {
        if (origem == null || destino == null) {
            return false;
        }

        if (!origem.isFile()) {
            log.warn("O arquivo de origem nao "
                    + "existe para ser copiado: " + origem);
            return false;
        }
        try {
            if (!ArquivoUtil.garantirExistencia(destino)) {
                return false;
            }
            return ArquivoUtil.salvar(destino, ArquivoUtil.ler(origem, charset),
                    charset);
        } catch (IOException ex) {
            log.error("IOException", ex);
            return false;
        }

    }

    public static boolean copiar(File origem, File destino)
            throws IOException {
        return copiar(origem, destino, "UTF-8");
    }

    /**
     * Apaga o arquivo XML.
     * @param caminhoArquivo - caminho do arquivo a ser apagado.
     */
    public static boolean apagar(String caminhoArquivo) {
        if (caminhoArquivo == null) {
            return false;
        }

        if (new File(caminhoArquivo).delete()) {
            log.debug("Arquivo apagado com sucesso.");
            return true;
        } else {
            log.warn("Arquivo nao pode ser apagado.");
            return false;
        }
    }

    /**
     * Esvazia o conteudo de um diretorio e apaga o diretorio.
     * @param caminhoDiretorio
     * @return true apenas se a operacao foi bem sucedida.
     */
    public static boolean apagarDiretorio(File caminhoDiretorio) {
        if (caminhoDiretorio == null) {
            return false;
        }

        if (!limparDiretorio(caminhoDiretorio)) {
            log.warn("Nao foi possivel esvaziar o diretorio: "
                    + caminhoDiretorio.getAbsolutePath());
            return false;
        }

        return caminhoDiretorio.delete();
    }

    /**
     * Apaga todos os arquivos, pastas e subpastas de um diretorio.
     * @param caminhoDiretorio
     * @return true apenas se a operacao foi bem sucedida.
     */
    public static boolean limparDiretorio(File caminhoDiretorio) {
        if (caminhoDiretorio == null) {
            return false;
        }

        if (!caminhoDiretorio.isDirectory()) {
            if (caminhoDiretorio.isFile()) {
                throw new IllegalArgumentException("O parametro especificado "
                        + "aponta para um arquivo, e nao para um diretorio.");
            } else {
                log.warn("O diretorio nao existe para que seja "
                        + "apagado: " + caminhoDiretorio.getAbsolutePath());
                return false;
            }
        }
        File[] oldFiles = caminhoDiretorio.listFiles();
        for (int i = 0; i < oldFiles.length; i++) {
            if (oldFiles[i].isFile()) {
                if (!oldFiles[i].delete()) {
                    log.warn("Nao foi possivel apagar o arquivo: "
                            + oldFiles[i].getAbsolutePath());
                }
            } else {
                if (!apagarDiretorio(oldFiles[i])) {
                    log.warn("Nao foi possivel apagar o subdiretorio:"
                            + oldFiles[i].getAbsolutePath());
                    return false;
                }
            }
        }
        return caminhoDiretorio.listFiles().length == 0;
    }

    /**
     * Mantido por razoes de compatibilidade com sistemas (NFE).
     * @param in
     * @return
     * @throws IOException
     * @see gov.sefaz.ArquivoUtil.general.Arquivo#lerInputStream(InputStream)
     */
    public static byte[] toByteArray(ZipInputStream in) throws IOException {
        return toByteArray((InputStream) in);
    }

    public static byte[] toByteArray(InputStream in) throws IOException {

        if (in == null) {
            return null;
        }

        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        byte bytes[] = new byte[2048];
        int bytesRead = -1;
        try {
            while ((bytesRead = in.read(bytes)) != -1) {
                buffer.write(bytes, 0, bytesRead);
            }
        } catch (IOException ex) {
            log.warn("IOException", ex);
        }
        return buffer.toByteArray();

    }

    public static String ler(File aFile) {
        return ler(aFile, "UTF-8", true);
    }

    public static String ler(File aFile, String charset) {
        return ler(aFile, charset, true);
    }

    /**
     * Fetch the entire contents of a text file, and return it in a String.
     * This style of implementation does not throw Exceptions to the caller.
     *
     * @param aFile is a file which already exists and can be read.
     */
    public static String ler(File aFile, String charset, boolean systemLineSeparator) {
        //...checks on aFile are elided
        StringBuffer contents = new StringBuffer();

        if (aFile == null) {
            return null;
        }

        //declared here only to make visible to finally clause

        BufferedReader input = null;
        FileInputStream fileInput = null;
        InputStreamReader reader = null;
        try {
            fileInput = new FileInputStream(aFile);
            //use buffering
            //this implementation reads one line at a time
            //FileReader always assumes default encoding is OK!
            reader = null;
            if (charset == null || charset.trim().isEmpty()) {
                return null;
            } else {
                reader = new InputStreamReader(
                        fileInput, charset);
            }
            if (systemLineSeparator) {
                input = new BufferedReader(reader);
                String line = input.readLine(); //not declared within while loop
                while (line != null) {
                    contents.append(line);
                    if ((line = input.readLine()) != null) {
                        contents.append(System.getProperty("line.separator"));
                    }
                }
            } else {
                if (aFile.length() > Integer.MAX_VALUE) {
                    throw new IllegalArgumentException("O tamanho do arquivo "
                            + "excede o limite maximo de " + Integer.MAX_VALUE
                            + " bytes");
                }
                char[] chars = new char[(int) aFile.length()];
                reader.read(chars);
                return new String(chars).trim();
            }
        } catch (FileNotFoundException ex) {
            log.error("Erro", ex);
        } catch (IOException ex) {
            log.error("Erro", ex);
        } finally {
            try {
                if (input != null) {
                    //flush and close both "input" and its underlying FileReader
                    input.close();
                }
                if (fileInput != null) {
                    fileInput.close();
                }
                if (reader != null) {
                    reader.close();
                }
            } catch (IOException ex) {
                log.error(ex.getMessage(), ex);
            }
        }
        return contents.toString();
    }

    /**
     * Valida o tamanho do arquivo quanto ao tamanho limite
     * @param arquivo File
     * @throws Exception
     */
    public static boolean tamanhoLimite(File arquivo, long tamanhoLimiteKB) {

        if (arquivo == null) {
            return false;
        }

        long tamanho = arquivo.length();

//        if (tamanho == 0) {
//            return false;
//        }

        int tamanhoKB = (int) (tamanho / 1024);

        if (tamanhoKB >= tamanhoLimiteKB) { // Tamanho em KB
            return false;
        }
        return true;
    }

    /**
     *  Retorna o array de bytes lido do arquivo especificado.
     */
    public static byte[] toByteArray(File file) throws IOException {

        if (file == null || !file.exists()) {
            return null;
        }

        FileInputStream f = null;
        byte[] b = null;
        try {
            b = new byte[(int) file.length()];
            f = new FileInputStream(file);
            f.read(b);
        } catch (Exception ex) {
            log.error("Erro", ex);
        } finally {
            try {
                f.close();
            } catch (IOException ex) {
                log.error("Erro", ex);
            }
        }
        return b;
    }

    /**
     *  Retorna o array de bytes lido do arquivo especificado.
     */
    public static byte[] toByteArray(String caminhoArquivo)
            throws FileNotFoundException, IOException {
        if (caminhoArquivo == null) {
            return null;
        }

        return toByteArray(new File(caminhoArquivo));
    }

    /**
     *
     * @param arquivo
     * @param createDirectory especifica se deve ser garantida a existencia de
     * 			um diretorio ou, do contrario, de um arquivo.
     * @throws IOException
     */
    public static boolean garantirExistencia(File arquivo, boolean createDirectory)
            throws IOException {
        if (arquivo == null) {
            log.warn("O arquivo nao pode ser null!");
            return false;
        }
        if (!arquivo.exists()) {
            if (createDirectory) {
                if (!arquivo.mkdirs()) {
                    return false;
                }
            } else {
                if (!(arquivo.getParentFile() == null)
                        && !arquivo.getParentFile().isDirectory()) {
                    if (!arquivo.getParentFile().mkdirs()) {
                        return false;
                    }
                }
                if (!arquivo.createNewFile()) {
                    return false;
                }
            }
        } else {
            if (createDirectory && arquivo.isFile()) {
                log.warn("Existe um arquivo com o nome do "
                        + "diretorio cuja criacao foi solicitada: "
                        + arquivo.getAbsolutePath());
                return false;
            } else if (!createDirectory && arquivo.isDirectory()) {
                log.warn("Existe uma pasta com o nome do "
                        + "arquivo cuja criacao foi solicitada.");
                return false;
            }
        }
        return true;
    }

    public static boolean garantirExistencia(File arquivo) throws IOException {
        return garantirExistencia(arquivo, false);
    }

    public static String garantirBarraInicio(String path) {
        if (path == null) {
            return null;
        }

        if (!(path.startsWith("/") || path.startsWith("\\") || path.indexOf(":") > 0)) {
            path = ((path.indexOf("\\") > 0) ? "\\" : "/") + path;
        }
        return path;
    }

    public static String garantirBarraFim(String dirPath) {
        if (dirPath == null) {
            return null;
        }
        if (!dirPath.matches(".*[\\\\/]")) {
            dirPath += dirPath.indexOf('\\') >= 0 ? "\\" : "/";
        }
        return dirPath;
    }

    public static String removerBarraInicio(String path) {
        if (path == null) {
            return null;
        }

        if (path.startsWith("/") || path.startsWith("\\")) {
            path = path.substring(1);
        } else if (path.indexOf(":") > 0) {
            path = path.substring(path.indexOf(":") + 1);
        }
        return path;
    }

    /**
     * Procura em todo o <i>classpath</i> um arquivo (que nao pode ser um
     * diretorio) correspondente ao <code>caminhoRelativo</code> informado.
     * Se o arquivo nao for encontrado, uma <code>FileNotFoundException</code> e
     * lancada.
     * @param caminhoRelativo o caminho para um arquivo ou diretorio existente.
     * @return o File correspondente ao caminho relativo informado.
     * @throws FileNotFoundException se o <code>caminhoRelativo</code> nao
     * 		corresponde a um arquivo existente.
     */
    public static File getArquivoRelativoClasspath(String caminhoRelativo)
            throws FileNotFoundException {

        URL url = null;

        try {
            url = ArquivoUtil.class.getResource(caminhoRelativo);
        } catch (Exception e) {
            log.error("Erro", e);
        }

        if (url == null) {
            return null;
        }
        return new File(url.getPath());
    }

    public static InputStream getInputStreamRelativoClasspath(
            String caminhoRelativo) throws FileNotFoundException {

        if (caminhoRelativo == null) {
            return null;
        }

        InputStream is = ArquivoUtil.class.getResourceAsStream(caminhoRelativo);
        return is;
    }

    public static boolean zip(String arqSaida, String diretorio) {
        int i, cont;
        byte[] dados = new byte[TAMANHO_BUFFER];
        String arquivos[];
        File f = null;
        BufferedInputStream origem = null;
        FileInputStream streamDeEntrada = null;
        FileOutputStream destino = null;
        ZipOutputStream saida = null;
        ZipEntry entry = null;

        try {
            destino = new FileOutputStream(arqSaida);
            saida = new ZipOutputStream(new BufferedOutputStream(destino));
            f = new File(diretorio); // Todos os arquivos da pasta onde a classe esta
            arquivos = f.list();

            for (i = 0; i < arquivos.length; i++) {
                File arquivo = new File(diretorio + arquivos[i]);

                if (arquivo.isFile()) {
                    log.debug("Compactando: " + arquivos[i]);

                    streamDeEntrada = new FileInputStream(arquivo);
                    origem = new BufferedInputStream(streamDeEntrada,
                            TAMANHO_BUFFER);
                    entry = new ZipEntry(diretorio + arquivos[i]);
                    saida.putNextEntry(entry);

                    while ((cont = origem.read(dados, 0, TAMANHO_BUFFER))
                            != -1) {
                        saida.write(dados, 0, cont);
                    }

                    origem.close();
                }
            }

            saida.close();
            return true;

        } catch (Exception e) {
            log.error("Erro", e);
            e.printStackTrace();
            return false;
        }
    }

    /**
     * Comprime o conte�do do arquivo <code>file</code> retornando-o em um
     * ByteArrayOutputStream.
     * @param file
     * @param level o n�vel de compressao (0 - sem compress�o; 9 - compress�o m�xima).
     * @return nada.
     * @author Jonathas Carrijo
     * @throws IOException
     */
    public static ByteArrayOutputStream zip(File file, int level)
            throws IOException {

        if (file == null) {
            return null;
        }

        String entryName = file.getName();
        FileInputStream fis = new FileInputStream(file);
        return zip(fis, level, entryName);
    }

    public static ByteArrayOutputStream zip(InputStream is, int level,
            String entryName) throws IOException {

        if (is == null || entryName == null || entryName.isEmpty()) {
            return null;
        }

        ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
        ZipOutputStream zipOutputStream = new ZipOutputStream(baos);
        zipOutputStream.putNextEntry(new ZipEntry(entryName));
        zipOutputStream.setLevel(level);

        byte[] buffer = new byte[8192];
        int bytesRead;
        while ((bytesRead = is.read(buffer)) != -1) {
            zipOutputStream.write(buffer, 0, bytesRead);
        }
        is.close();
        zipOutputStream.close();
        return baos;
    }

    public static byte[] zip(byte[] arquivo, int level,
            String entryName) throws IOException {

        if (arquivo == null || entryName == null || entryName.isEmpty()) {
            return null;
        }

        try {
            ByteArrayInputStream is = new ByteArrayInputStream(arquivo);
            ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
            ZipOutputStream zipOutputStream = new ZipOutputStream(baos);
            zipOutputStream.putNextEntry(new ZipEntry(entryName));
            zipOutputStream.setLevel(level);

            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1) {
                zipOutputStream.write(buffer, 0, bytesRead);
            }
            is.close();
            zipOutputStream.close();
            return baos.toByteArray();
        } catch (IOException ex) {
            log.error("Erro", ex);
            return null;
        }
    }
    
    /**
     * Nome do arquivo fixado para arquivo.xml, portanto não precisa passar no parâmetro
     * @param arquivo
     * @param level
     * @return arquivo zipado em byte[]
     * @throws IOException
     * 
     * @author gaspar
     */
    public static byte[] zip(byte[] arquivo, int level) throws IOException {

        if (arquivo == null) {
            return null;
        }

        try {
            ByteArrayInputStream is = new ByteArrayInputStream(arquivo);
            ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
            ZipOutputStream zipOutputStream = new ZipOutputStream(baos);
            zipOutputStream.putNextEntry(new ZipEntry("arquivo.xml"));
            zipOutputStream.setLevel(level);

            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1) {
                zipOutputStream.write(buffer, 0, bytesRead);
            }
            is.close();
            zipOutputStream.close();
            return baos.toByteArray();
        } catch (IOException ex) {
            log.error("Erro", ex);
            return null;
        }
    }

    public static byte[] zipByte(File file, int level)
            throws IOException {

        if (file == null) {
            return null;
        }

        String entryName = file.getName();
        FileInputStream fis = new FileInputStream(file);
        //ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ByteArrayOutputStream baos = zip(fis, level, entryName);
        return baos.toByteArray();
    }

    public static byte[] zipByte(InputStream is, int level, String entryName)
            throws IOException {

        ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);

        baos = zip(is, level, entryName);

        if (baos == null) {
            return null;
        }

        return baos.toByteArray();
    }

    public static byte[] unzip(InputStream input) throws IOException {

        if (input == null) {
            return null;
        }

        ZipInputStream zis = new ZipInputStream(input);
        zis.getNextEntry();
        return toByteArray(zis);
    }

    public static String unzip(byte[] arquivo) throws IOException {
        if (arquivo == null) {
            return null;
        }

        return new String(ArquivoUtil.unzip(new ByteArrayInputStream(arquivo)), "UTF-8");
    }

    /**Le arquivo e carrega linha a linha numa linkedlist
     * 
     * @param aFile
     * @param charset
     * @param systemLineSeparator
     * @return
     * 
     * @author gaspar
     */
    public static LinkedList<String> lerArquivo(File aFile) {
        LinkedList<String> lista = new LinkedList<String>();

        if (aFile == null) {
            return null;
        }

        BufferedReader input = null;
        FileInputStream fileInput = null;
        InputStreamReader reader = null;
        try {
            fileInput = new FileInputStream(aFile);
            reader = null;
            reader = new InputStreamReader(fileInput);

            input = new BufferedReader(reader);
            String line = null;
            while ((line = input.readLine()) != null) {
                lista.add(line);
            }
        } catch (FileNotFoundException ex) {
            log.error("Erro", ex);
        } catch (IOException ex) {
            log.error("Erro", ex);
        } finally {
            try {
                if (input != null) {
                    input.close();
                }
                if (fileInput != null) {
                    fileInput.close();
                }
                if (reader != null) {
                    reader.close();
                }
            } catch (IOException ex) {
                log.error(ex.getMessage(), ex);
            }
        }
        return lista;
    }

    /**
     * Converte inputStream em String.
     * @param is : inputStream a ser convertido.
     * @return : retorna a string.
     * @throws IOException :
     */
    public static String toString(InputStream is)
            throws IOException {

        byte[] result = toByteArray(is);

        if(result == null)
            return null;
        
        return new String(result, "UTF-8");
    }
}
